这一步中,您将实现拖放功能。
在本节中,您将首先使用 Kanzi Engine API 实例化您在本教程上一步创建的 Drag Item 预设件模板。然后定义用户开始拖放手势和拖动按钮时的行为。最后,您将创建并配置导航栏中各按钮的拖放操纵器。
要创建拖动功能:
onProjectLoaded
() 函数中实例化用于可视化用户正在拖动的按钮的Drag Item 预设件:virtual void onProjectLoaded() KZ_OVERRIDE { ... //获取 Drag Item 预设件的引用。 ResourceManager* resourceManager = getDomain()->getResourceManager(); PrefabTemplateSharedPtr dragItemPrefab = resourceManager->acquireResource<PrefabTemplate>("kzb://drag_and_drop/Prefabs/Drag Item"); //实例化 Drag Item 预设件。 m_dragItem = dragItemPrefab->instantiate<Node2D>("Drag Item"); //获取使用其别名的 RootPage 节点。 Node2DSharedPtr rootPage = screen->lookupNode<Node2D>("#RootPage"); //为RootPage 节点添加您创建的 Drag Item 预设件实例。 rootPage->addChild(m_dragItem); //禁用 Drag Item 预设件实例的可见性 (Visible) 属性。 //当用户不拖动 Drag Item 时,将其隐藏。 m_dragItem->setVisible(false); } private: ... //为实例化的 Drag Item 定义成员变量。 Node2DSharedPtr m_dragItem;
DragAndDrop
类的私有部分创建移动 Drag Item 预设件实例的函数:private: ... //更新 Drag Item 的位置。 void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform) { //计算局部拖动锚点,即用户正在拖动的按钮左上角。 Vector2 localDragAnchor = dragPosition - m_dragGrabOffset; //将 Drag Item 的移动限制到 x 轴。 localDragAnchor.setY(0.0f); //计算全局拖动锚点。 Vector2 globalDragAnchor = dragWorldTransform * localDragAnchor; //用于描述 Drag Item 的 渲染变换 (Render Transformation) 属性的结构。 SRTValue2D transform; //设置渲染变换 (Render Transformation) 属性平移 (Translation) 属性字段为全局拖动锚点。 transform.setTranslation(globalDragAnchor); //移动 Drag Item 拖动的距离量。 m_dragItem->setRenderTransformation(transform); //设置按钮图标。 updateItems(); } ... //定义用户按下或点击按钮时该按钮左上角偏移的成员变量。 Vector2 m_dragGrabOffset; };
DragAndDrop
类的私有部分定义 DragAndDropManipulator::StartedMessage
消息的处理程序:private:
...
// 定义 2D 节点的 DragAndDropManipulator::StartedMessage
消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
//这样可为拖动准备 2D 节点。
void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
{
//从消息参数获得用户开始拖动的按钮。
Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
//获取用户开始拖动的按钮大小。
Vector2 dragSourceItemSize = dragSourceItem->getActualSize();
//将 Drag Item 设置为和该按钮相同的大小。
m_dragItem->setSize(dragSourceItemSize.getX(), dragSourceItemSize.getY());
//移动 Drag Item 到正确的位置。
updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
//让 Drag Item 可见。
m_dragItem->setVisible(true);
}
...
onDragStarted
函数后面定义 DragAndDropManipulator::MovedMessage
消息的处理程序: // 定义 2D 节点的 DragAndDropManipulator::MovedMessage
消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
void onDragMoved(DragAndDropManipulator::MovedMessageArguments& messageArguments)
{
//从消息参数获得用户正在拖动的按钮。
Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
//移动 Drag Item 并更新按钮图标。
updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
}
DragAndDrop
类的私有部分添加创建和配置节点拖放操纵器的函数:// 创建并配置节点的拖放操纵器。 void createDragAndDropManipulator(NodeSharedPtr dragSourceItem) { Domain* domain = getDomain(); //创建生成拖放消息的输入操纵器。 DragAndDropManipulatorSharedPtr dragAndDropManipulator = DragAndDropManipulator::create(domain); //添加输入操纵器到该节点。 dragSourceItem->addInputManipulator(dragAndDropManipulator); //在拖放开始前设置长按持续时间为 200 ms。默认值为 500 ms。 //这是用户在开始拖动节点前必须按下节点的时间。 dragAndDropManipulator->setPressDuration(chrono::milliseconds(200)); //订阅该节点的DragAndDropManipulator::StartedMessage
消息。 //DragAndDropManipulator
在用户按下该节点 //获取 DragAndDropManipulator::setPressDuration 设置的持续时间时生成此消息。 dragSourceItem->addMessageHandler(DragAndDropManipulator::StartedMessage, bind(&DragAndDrop::onDragStarted, this, placeholders::_1)); //订阅该节点的DragAndDropManipulator::MovedMessage
消息。 //DragAndDropManipulator
在指针移动时生成此消息。 dragSourceItem->addMessageHandler(DragAndDropManipulator::MovedMessage, bind(&DragAndDrop::onDragMoved, this, placeholders::_1)); }
onProjectLoaded()
函数末尾为各按钮调用 createDragAndDropManipulator
函数:virtual void onProjectLoaded() KZ_OVERRIDE { ... //创建各按钮的拖放操纵器。 //使用别名获取按钮节点。 createDragAndDropManipulator(screen->lookupNode<Node>("#Navigation")); createDragAndDropManipulator(screen->lookupNode<Node>("#Phone")); createDragAndDropManipulator(screen->lookupNode<Node>("#Applications")); createDragAndDropManipulator(screen->lookupNode<Node>("#Music")); createDragAndDropManipulator(screen->lookupNode<Node>("#Car")); }
这一节中,您将设置用户拖动的按钮图标和位置,并在用户拖动其中一个按钮时重定位按钮图标。
要完成拖动功能:
onDragStarted
函数中,在调用 updateDragAndDrop
函数之前,设置 Drag Item 的图标并正确定位该节点:void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments) { ... //获取用户开始拖动的按钮的数据上下文对象。 m_draggedDataContext = dynamic_pointer_cast<DataObject>(dragSourceItem->getProperty(DataContext::DataContextProperty)); //将 Drag Item 的数据上下文 (Data Context) 属性设置为该按钮的数据上下文。 //这样即可将 Drag Item 设置为具有和用户开始拖动的按钮相同的图标。 m_dragItem->setProperty(DataContext::DataContextProperty, m_draggedDataContext); //保存用户相对于节点原点(默认情况下为左上角) //开始拖动节点的起点。 m_dragGrabOffset = messageArguments.getPoint(); ... } ... //为用户拖动的按钮的数据上下文对象定义成员变量。 DataObjectSharedPtr m_draggedDataContext; };
updateDragAndDrop
函数中,在调用 updateItems()
函数之前,添加代码,在用户拖动其中一个按钮时重定位所有按钮的图标:void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform) { ... //获取全局指针位置。 Vector2 globalPointerPosition = dragWorldTransform * dragPosition; //将全局坐标转换为 2D 网格布局 (Grid layout 2D) 节点的本地坐标。 Vector2 hitTestPoint = *m_grid->globalToLocal(globalPointerPosition); //获取按钮的宽度。 float cellWidth = m_grid->getActualColumnSize(0); //计算2D 网格布局 (Grid layout 2D) 节点中按钮的索引。 unsigned int cellIndex = 0; if (hitTestPoint.getX() > 0.0f) { cellIndex = static_cast<unsigned int>(hitTestPoint.getX() / cellWidth); cellIndex = min(cellIndex, m_grid->getChildCount() - 1u); } //从旧位置移除数据对象。 m_rootData->removeChild(*m_draggedDataContext); //插入数据对象到新位置。 m_rootData->insertChild(cellIndex, m_draggedDataContext); ... }
updateItems()
函数中,在 for
循环中添加 if-else
从句,在用户拖动时隐藏按钮图标: void updateItems()
{
...
for (; dataIt != endDataIt; dataIt++, nodeIt++)
{
...
//如果按钮节点是用户正在拖动的节点,将其隐藏。
//隐藏该节点,因为使用 Drag Item 可视化该节点的拖动。
if (m_draggedDataContext && itemData == m_draggedDataContext)
{
itemNode->setVisible(false);
}
else
{
itemNode->setVisible(true);
}
}
}
在之前的部分,实现了按钮的拖放。当用户结束拖放手势时,Drag Item 在用户释放指针的位置保持可见。在本节中,您将添加代码,让按钮看上去像落入用户放置它的位置。
要创建放置功能:
onDragMoved
函数后面定义 DragAndDropManipulator::FinishedMessage
消息的处理程序: // 定义 2D 节点的 DragAndDropManipulator::FinishedMessage
消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
void onDragFinished(DragAndDropManipulator::FinishedMessageArguments&)
{
//隐藏 Drag Item 预设件实例。
m_dragItem->setVisible(false);
//清除至数据上下文对象的指针。
m_draggedDataContext.reset();
//分配正确的图标到按钮。
updateItems();
}
createDragAndDropManipulator
函数的末尾订阅该节点的 DragAndDropManipulator::FinishedMessage
消息:// 创建并配置节点的拖放操纵器。 void createDragAndDropManipulator(NodeSharedPtr dragAndDropNode) { ... //订阅该节点的DragAndDropManipulator::FinishedMessage
消息。 //当用户松开指针结束拖放手势时,DragAndDropManipulator
//生成此消息。 dragSourceItem->addMessageHandler(DragAndDropManipulator::FinishedMessage, bind(&DragAndDrop::onDragFinished, this, placeholders::_1)); }
构建和运行应用程序。
结束拖放手势时,Drag Item 变得不可见。
要详细了解拖放输入操纵器,请参阅使用拖放操纵器。
要详细了解关于在 Kanzi 中处理用户输入的详细信息,请参阅处理用户输入。